package socks

import (
	"errors"
	"fmt"
	"gitee.com/simple-set/simple.io/src/event"
	"gitee.com/simple-set/simple.io/src/process/codec"
	"io"
	"reflect"
)

type Process struct {
	connect        ConnectCommand
	bind           BindCommand
	associate      AssociateCommand
	authentication *Authentication
	interceptor    []Interceptor // 拦截器
}

func NewProcess() *Process {
	return &Process{authentication: NewAuthentication()}
}

func (s *Process) Accept(session *event.Session) error {
	handshake := NewHandshake(codec.NewReadWriteByteBuf(session.Socket.Reader, session.Socket.Writer))
	if err := handshake.Decoder(); err != nil {
		return errors.New("Handshake decoding failed, " + err.Error())
	}

	handshake.session = session
	session.Logger = session.Logger.With("version", handshake.version)
	session.Logger.Debug("Socks handshake successful")
	session.SetAttr("handshake", handshake)
	return s.authentication.Auth(handshake)
}

func (s *Process) Active(session *event.Session, _ any) (any, error) {
	handshake := session.GetAttr("handshake").(*Handshake)
	response := NewResponse(handshake, session)
	request := NewRequest(handshake, session)

	if err := request.Decoder(); err != nil {
		_ = response.Reply(ServerFailure)
		return nil, errors.New("Request decoding failed, " + err.Error())
	}

	session.Logger = session.Logger.With("mode", request.Cmd)
	if err := s.RuleCheck(request); err != nil {
		_ = response.Reply(RuleFailure)
		return nil, io.EOF
	}
	return nil, s.handler(request, response)
}

func (s *Process) RuleCheck(request *Request) error {
	if s.interceptor != nil {
		for _, interceptor := range s.interceptor {
			if interceptor.Intercept(request) {
				return errors.New(reflect.ValueOf(interceptor).Elem().Type().String())
			}
		}
	}
	return nil
}

func (s *Process) handler(request *Request, response *Response) (err error) {
	request.session.Logger.Debug("Received socks request")
	defer func() {
		if panicValue := recover(); panicValue != nil {
			switch panicValue.(type) {
			case error:
				err = panicValue.(error)
			default:
				err = fmt.Errorf("unknown exception, %v", panicValue)
			}
			_ = response.Reply(ServerFailure)
		}
	}()

	if request.Cmd == Connect && s.connect != nil {
		return s.connect.Connect(request, response)
	}
	if request.Cmd == Bind && s.bind != nil {
		return s.bind.Bind(request, response)
	}
	if request.Cmd == Associate && s.associate != nil {
		return s.associate.Associate(request, response)
	}
	_ = response.Reply(CommandNotSupported)
	return errors.New(fmt.Sprint("unsupported command: ", request.Cmd))
}

func (s *Process) SetAssociate(associate AssociateCommand) {
	s.associate = associate
}

func (s *Process) SetBind(bind BindCommand) {
	s.bind = bind
}

func (s *Process) SetConnect(connect ConnectCommand) {
	s.connect = connect
}

// AddAuthenticator 注册认证器
func (s *Process) AddAuthenticator(authenticator Authenticator) {
	s.authentication.AddAuthenticator(authenticator)
}

func (s *Process) AddInterceptor(interceptor Interceptor) {
	s.interceptor = append(s.interceptor, interceptor)
}

// SetNoAuth 是否允许无认证
func (s *Process) SetNoAuth(noAuth bool) {
	s.authentication.SetNoAuth(noAuth)
}
